﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Diagnostics;
using DT = System.Func<object, string>;
using MyUtils;
using System.Reflection.Emit;
using System.Reflection;

namespace TestMyUtils
{
	[TestClass]
	public class TestDynamicInvokeNet
	{
		[TestMethod]
		public void Should_call_static_method()
		{
			var dm = new DynamicMethod(string.Empty, typeof(string), new Type[0]);
			var il = dm.GetILGenerator();
			il.Emit(OpCodes.Call, GetType().GetMethod("test_static"));
			il.Emit(OpCodes.Ret);
			Assert.AreEqual("test_static_pass", ((Func<string>)dm.CreateDelegate(typeof(Func<string>)))());
		}

		public static string test_static()
		{
			return "test_static_pass";
		}

		[TestMethod]
		public void Should_call_void_static_method()
		{
			check = 3;
			var dm = new DynamicMethod(string.Empty, typeof(void), new Type[0]);
			var il = dm.GetILGenerator();
			il.Emit(OpCodes.Call, GetType().GetMethod("test_void_static"));
			il.Emit(OpCodes.Ret);
			((Action)dm.CreateDelegate(typeof(Action)))();
			Assert.AreEqual(5, check);
		}

		static int check = 4;

		public static void test_void_static()
		{
			check = 5;
			return;
		}

		[TestMethod]
		public void Should_call_inst_method()
		{
			var mi = GetType().GetMethod("get_test_inst");

			var args = new List<Type>();
			args.Add(typeof(object));
			args.AddRange(mi.GetParameters().Select(x => x.ParameterType));

			var dm = new DynamicMethod(string.Empty, mi.ReturnType, args.ToArray(), GetType());
			var il = dm.GetILGenerator();

			for (int i = 0; i < args.Count; i++)
			{
				il.Emit(OpCodes.Ldarg, i);
			}
			il.Emit(OpCodes.Call, mi);
			il.Emit(OpCodes.Ret);

			var del = (DT)dm.CreateDelegate(typeof(DT));
			Assert.AreEqual(test + "_test_inst_pass", del(this));
		}

		public string get_test_inst()
		{
			return test + "_test_inst_pass";
		}

		Guid test = Guid.NewGuid();

	}

	[TestClass]
	public class TestDynamicInvokeOptimizerUnboundDelegate
	{
		delegate long MyTest(long q);

		Action a1 = delegate { };
		Action<int> a2 = delegate { };
		Action<int, string> a3 = delegate { };
		Func<int> a4 = delegate { return 0; };
		Func<string, int> a5 = delegate { return 0; };
		Func<string, long, int> a6 = delegate { return 0; };
		EventHandler<PropertyChangedEventArgs> a7 = delegate { };
		MyTest a8 = delegate { return 0; };

		[TestMethod]
		public void Should_return_unbound_delegate_type()
		{
			var sw = Stopwatch.StartNew();
			for (int i = 0; i < 10000; i++)
			{
				Assert.AreEqual(typeof(Action<object>), DynamicInvokeOptimizer.GetUnboundDelegate(a1));
				Assert.AreEqual(typeof(Action<object, int>), DynamicInvokeOptimizer.GetUnboundDelegate(a2));
				Assert.AreEqual(typeof(Action<object, int, string>), DynamicInvokeOptimizer.GetUnboundDelegate(a3));

				Assert.AreEqual(typeof(Func<object, int>), DynamicInvokeOptimizer.GetUnboundDelegate(a4));
				Assert.AreEqual(typeof(Func<object, string, int>), DynamicInvokeOptimizer.GetUnboundDelegate(a5));
				Assert.AreEqual(typeof(Func<object, string, long, int>), DynamicInvokeOptimizer.GetUnboundDelegate(a6));

				Assert.AreEqual(typeof(Action<object, object, PropertyChangedEventArgs>), DynamicInvokeOptimizer.GetUnboundDelegate(a7));

				Assert.AreEqual(typeof(Func<object, long, long>), DynamicInvokeOptimizer.GetUnboundDelegate(a8));
			}
			sw.Stop();
			Console.WriteLine(sw.Elapsed.ToSpanString());
			Assert.IsTrue(sw.ElapsedMilliseconds < 100, sw.Elapsed.ToSpanString());
		}

		[TestMethod]
		public void Should_return_signature_from_delegate()
		{
			var sig = DynamicInvokeOptimizer.GetDelegateSignature(a2);
			Assert.AreEqual(typeof(void), sig.Item1);
			Assert.AreEqual(1, sig.Item2.Length);
			Assert.AreEqual(typeof(int), sig.Item2[0]);

			sig = DynamicInvokeOptimizer.GetDelegateSignature(a5);
			Assert.AreEqual(typeof(int), sig.Item1);
			Assert.AreEqual(1, sig.Item2.Length);
			Assert.AreEqual(typeof(string), sig.Item2[0]);

			sig = DynamicInvokeOptimizer.GetDelegateSignature(a7);
			Assert.AreEqual(typeof(void), sig.Item1);
			Assert.AreEqual(2, sig.Item2.Length);
			Assert.AreEqual(typeof(object), sig.Item2[0]);
			Assert.AreEqual(typeof(PropertyChangedEventArgs), sig.Item2[1]);

			sig = DynamicInvokeOptimizer.GetDelegateSignature(a8);
			Assert.AreEqual(typeof(long), sig.Item1);
			Assert.AreEqual(1, sig.Item2.Length);
			Assert.AreEqual(typeof(long), sig.Item2[0]);

		}

		[TestMethod]
		public void Should_return_delegate_from_signature()
		{
			Assert.AreEqual(typeof(Action), DynamicInvokeOptimizer.GetDelegateTypeFromSignature(typeof(void)));
			Assert.AreEqual(typeof(Action<int>), DynamicInvokeOptimizer.GetDelegateTypeFromSignature(typeof(void), typeof(int)));
			Assert.AreEqual(typeof(Action<int, long>), DynamicInvokeOptimizer.GetDelegateTypeFromSignature(typeof(void), typeof(int), typeof(long)));

			Assert.AreEqual(typeof(Func<string>), DynamicInvokeOptimizer.GetDelegateTypeFromSignature(typeof(string)));
			Assert.AreEqual(typeof(Func<int, string>), DynamicInvokeOptimizer.GetDelegateTypeFromSignature(typeof(string), typeof(int)));
			Assert.AreEqual(typeof(Func<long, int, string>), DynamicInvokeOptimizer.GetDelegateTypeFromSignature(typeof(string), typeof(long), typeof(int)));
		}
	}

	[TestClass]
	public class TestDynamicInvokeOptimizer
	{
		public string Test(string a, string b)
		{
			return a + ", бля, " + b + "!";
		}

		[TestMethod]
		public void Should_optimize_dynamic_call()
		{
			Assert.AreEqual("круг, бля, квадрат!", Test("круг", "квадрат"));

			var sw1 = Stopwatch.StartNew();
			for (int i = 0; i < short.MaxValue*10; i++)
			{
				Assert.AreEqual("круг, бля, квадрат!", GetType().GetMethod("Test").Invoke(this, new[] { "круг", "квадрат" }));
			}
			sw1.Stop();
			Trace.WriteLine(sw1.Elapsed.ToString());

			var sw2 = Stopwatch.StartNew();
			var di = DynamicInvokeOptimizer.Compile<Func<object,string,string,string>>(GetType(), "Test");
			for (int i = 0; i < short.MaxValue * 10; i++)
			{
				Assert.AreEqual("круг, бля, квадрат!", di(this, "круг", "квадрат"));
			}
			sw2.Stop();
			Trace.WriteLine(sw2.Elapsed.ToString());

			// более чем в 10 раз ускорение
			Assert.IsTrue(sw2.ElapsedMilliseconds * 10 < sw1.ElapsedMilliseconds);
		}

		[TestMethod]
		public void Should_work_with_void_calls()
		{
			check = 0;
			DynamicInvokeOptimizer.Compile<Action<TestDynamicInvokeOptimizer, int>>(GetType().GetMethod("Void"))(this, 1);
			Assert.AreEqual(1, check);
		}

		[TestMethod]
		public void Should_work_with_static_void_calls()
		{
			check = 0;
			DynamicInvokeOptimizer.Compile<Action<int>>(GetType().GetMethod("StaticVoid"))(2);
			Assert.AreEqual(2, check);
		}

		[TestMethod]
		public void Should_work_with_static_calls()
		{
			Assert.AreEqual("55",
				DynamicInvokeOptimizer.Compile<Func<int, string>>(GetType().GetMethod("Static"))(55));
		}

		static int check;

		public void Void(int r)
		{
			check = r;
		}

		public static void StaticVoid(int r)
		{
			check = r;
		}

		public static string Static(int r)
		{
			return r.ToString();
		}

	}
}
